package org.ecpkn.PAGridSim.core.tasksCreation;

import gridsim.GridSimStandardPE;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.ecpkn.PAGridSim.core.configuration.Configuration;

/**
 * @author Lin Zhou Module:Tasks Creation
 * 
 */

public abstract class PeriodTaskCreator {

	private static final Logger logger = LogManager
			.getLogger(PeriodTaskCreator.class);

	private ArrayList<Task> createdTasks;
	protected int taskSetSize;
	protected List<Double> taskLength;

	/**
	 * The general frequency in the simulation should be higher than the
	 * standard rating, or else you will get a single task unable to be finished
	 * by a single processor with any frequency(u>1)
	 */
	public static int STANDARDRATING;

	/**
	 * When we calculate the execution time in GridSim,we round it up to
	 * INT(0.5s turns out to be 1s), and a period too small causes failures,
	 * since a task of period=0.5 can never be satisfied
	 */
	public static int MINPERIOD;

	/**
	 * The experimentLength is the length of the jobs' execution Gant chart
	 */
	protected static int EXPERIMENTLENGTH;

	static {
		STANDARDRATING = Configuration.getInstance().getParameterInt(
				Configuration.Const.STANDARD_RATING);
		MINPERIOD = Configuration.getInstance().getParameterInt(
				Configuration.Const.MIN_PERIOD);
		EXPERIMENTLENGTH = Configuration.getInstance().getParameterInt(
				Configuration.Const.EXPERIMENT_LENGTH);
	}

	/**
	 * Define the task number of the task set
	 * 
	 * @return task size
	 */
	protected abstract int defineTaskSetSize();

	/**
	 * Define the execution time needed for the task when using a processor of
	 * standard rating
	 * 
	 * @return
	 */
	protected abstract double defineTaskLength(int taskNumber);

	/**
	 * Define the task's period, you should guarantee a period longer than the
	 * task's length, or else you will get a single task unable to be finished
	 * by a single processor with any frequency(u>1)
	 * 
	 * @param length
	 *            the period should be longer than the length
	 * @return
	 */
	protected abstract double defineTaskPeriod(int taskNumber);

	/**
	 * If you get a period smaller than MINPERIODE, implement this method in
	 * order to reDefine another one
	 * 
	 * @param TaskNumber
	 * @return new period
	 */
	protected abstract double reDefinePeriod(int TaskNumber);

	/**
	 * Define the number of job instances for the task
	 * 
	 * @return
	 */
	protected abstract int defineTaskRepeatNumber(int taskNumber);

	/**
	 * It should be a double in (0,1), and it's used with the experimentLength
	 * factor, so that we can make sure that the arriving time of every task is
	 * smaller than the experimentLength
	 * 
	 * @return task arriving time
	 * @throws Exception
	 */
	protected abstract double defineTaskArrivingTime(int taskNumber)
			throws Exception;

	/**
	 * create a task set, it can also be many task sets arriving in different
	 * time
	 * 
	 * @return createdTasks
	 * @throws Exception
	 */
	public ArrayList<Task> creatTasks() throws Exception {
		init();
		taskSetSize = this.defineTaskSetSize();
		taskLength = new ArrayList<Double>();
		GridSimStandardPE.setRating(STANDARDRATING);
		createdTasks = new ArrayList<Task>();
		double length;
		double period;
		for (int i = 0; i < taskSetSize; i++) {
			length = defineTaskLength(i);
			period = defineTaskPeriod(i);

			while (period < MINPERIOD) {
				period = reDefinePeriod(i);
			}
			// change the length representation from Time to a Instruction
			length = Configuration.getInstance().toDecimalFormatDouble(
					GridSimStandardPE.toMIs(length));
			int taskId = i;
			double taskArrivingTime = defineTaskArrivingTime(i);

			createdTasks.add(new Task(taskId, period, length, taskArrivingTime
					* EXPERIMENTLENGTH, defineTaskRepeatNumber(i)));
		}
		printCreatedTasks();
		return createdTasks;
	}

	public static ArrayList<Job> createSubmitTimeSortedJobs(
			ArrayList<Task> tasks) {
		ArrayList<Job> sortedJobs = new ArrayList<Job>();
		for (Task t : tasks) {
			for (Job j : t.getJobs_()) {
				sortedJobs.add(j);
			}
		}
		ComparatorArriveTime comparator = new ComparatorArriveTime();
		Collections.sort(sortedJobs, comparator);
		return sortedJobs;
	}

	/**
	 * print the result of the creation of tasks, only for tracing and debugging
	 */
	public void printCreatedTasks() {
		for (int i = 0; i < createdTasks.size(); i++) {
			logger.trace("Task submitted at\t"
					+ toDecimalFormat(createdTasks.get(i).getSubmitedTime_())
					+ "\tTask id:\t" + createdTasks.get(i).getTaskID_()
					+ "\tTask period:\t"
					+ toDecimalFormat(createdTasks.get(i).getPeriod_())
					+ "\tTask length:\t"
					+ toDecimalFormat(createdTasks.get(i).getLength_())
					+"\tRepeat number:\t"+createdTasks.get(i).getRepeatNumber_()+"\tEnd time:\t"+toDecimalFormat(createdTasks.get(i).getEndTime()));
		}
	}

	// TODO:move to an outil class
	private String toDecimalFormat(Double key) {
		return Configuration.getInstance().toDecimalFormat(key);
	}

	/**
	 * can be override
	 */
	protected void init() {

	}
}
